from typing import List, Union
import pyproj
from shapely.geometry import MultiLineString, MultiPoint, MultiPolygon
from shapely.ops import transform
from shapely.ops import unary_union as shapely_unary_union

from .geom import Geom


def project_geom(
    geom: Union[Geom, List[Geom]], src_epsg: str, dest_epsg: str
) -> Union[Geom, List[Geom]]:
    """Project a geometry or list of geometries

    Parameters
    ----------
    geom : Union[Geom, List[Geom]]
        A geometry or a list of geometry objects.
    src_epsg : str
        Source srs specified as a EPSG string, e.g, "EPSG:{epsg-code}"
    dest_epsg : str
        Destination srs specified as a EPSG string, e.g, "EPSG:{epsg-code}"

    Returns
    -------
    Union[Geom, List[Geom]]
        Projected geometry or list of projected geometries.
    """
    transformer = pyproj.Transformer.from_crs(src_epsg, dest_epsg, always_xy=True)
    if isinstance(geom, list):
        result = []
        for geom_tmp in geom:
            res_geom = transform(transformer.transform, geom_tmp._shapely_geom)
            result.append(Geom(res_geom))
        return result
    else:
        res_geom = transform(transformer.transform, geom._shapely_geom)
        return Geom(res_geom)


def to_multi_geom(geoms: List[Geom]) -> Geom:
    """Convert a list of `Point`, `LineString` or `Polygon` geometry objects to
    `MultiPoint`, `MultiLineString` or `MultiPolygon` object, respectively.

    Parameters
    ----------
    geoms : List[Geom]
        List of geometries to be combined into a their multi-geom version.

    Returns
    -------
    Geom
        The combined multi-geometry.

    Raises
    ------
    ValueError
        Raised if the input geometry is not a `Point`, `LineString` or `Polygon`.
        Notice for performance consideration we only check the first item.
    """
    geom_type = geoms[0]._shapely_geom.geom_type
    if geom_type == "Point":
        result = MultiPoint([geom._shapely_geom for geom in geoms])
    elif geom_type == "LineString":
        result = MultiLineString([geom._shapely_geom for geom in geoms])
    elif geom_type == "Polygon":
        result = MultiPolygon([geom._shapely_geom for geom in geoms])
    else:
        raise ValueError(
            "Unsupported geometry type {}, only support `Point`, `LineString` and `Polygon`".format(
                geom_type
            )
        )
    return result


def from_multi_geom(geom: Geom) -> List[Geom]:
    """Convert a multi-geom object (`MultiPoint`, `MultiLineString`, `MultiPolygon`) to a list of their single-geom objects
    (`Point`, `LineString`, `Polygon`).

    Parameters
    ----------
    geom : Geom
        The input multi-geom object.

    Returns
    -------
    List[Geom]
        List of single-geom objects.

    Raises
    ------
    ValueError
        Raised if the given `geom` is not a supported type of geometry.
    """
    supported_geom_types = (
        "MultiPoint",
        "MultiLineString",
        "MultiPolygon",
    )
    if geom._shapely_geom.geom_type not in supported_geom_types:
        raise ValueError(
            "Unsupported geometry type {}, only support {}".format(
                geom._shapely_geom.geom_type, supported_geom_types
            )
        )

    return [Geom(geom_tmp) for geom_tmp in geom._shapely_geom.geoms]


def unary_union(geoms: List[Geom]) -> Geom:
    """Union a list of Geoms, see `shapely.ops.unary_union` for more details.

    Parameters
    ----------
    geoms : List[Geom]
        A list of Geoms to be unionized.

    Returns
    -------
    Geom
        Resulting Geom.
    """
    return Geom(shapely_unary_union([item._shapely_geom for item in geoms]))


def random_points_within_geom(geom, n):
    pass
